package br.com.iswe.teste.dados;

import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Timestamp;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import javax.sql.DataSource;

import org.apache.commons.dbcp.BasicDataSource;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.ScalarHandler;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.poi.hssf.usermodel.HSSFCell;
import org.apache.poi.hssf.usermodel.HSSFRow;
import org.apache.poi.hssf.usermodel.HSSFSheet;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.poifs.filesystem.POIFSFileSystem;

import br.com.iswe.core.exp.ISWEBadCodeException;
import br.com.iswe.core.exp.ISWEConfigurationException;
import br.com.iswe.core.exp.ISWEException;
import br.com.iswe.teste.util.TestSuiteDataManager;

/**
 * @author  Rafael Noronha de Almeida.
 * @version $Revision: 1.2 $ $Date: 2008/09/04 20:27:15 $
 *
 * <PRE>
 *  <b>Descri��o:</b> Classe responsavel por ler dados de arquivos excel e carregalos em memoria
 * para disponibilizar para os test-cases..
 * </PRE>
 */
@SuppressWarnings("unchecked")
public class TestDataManager {

    private static final String ISWE_HTTPUNIT_POOLDADOS_BROWSER_TIMEMASK = "iswe.httpunit.pooldados.browser.timemask";
	private static final String ISWE_HTTPUNIT_POOLDADOS_BROWSER_DATETIMEMASK = "iswe.httpunit.pooldados.browser.datetimemask";
	private static final String ISWE_HTTPUNIT_POOLDADOS_BROWSER_DATEMASK = "iswe.httpunit.pooldados.browser.datemask";
	private static final String ISWE_HTTPUNIT_HOST_URL = "iswe.httpunit.host.url";
    private static final String ISWE_HTTPUNIT_POOLDADOS_XLS_DATETIMEMASK = "iswe.httpunit.pooldados.xls.datetimemask";
    private static final String ISWE_HTTPUNIT_POOLDADOS_XLS_DATEMASK = "iswe.httpunit.pooldados.xls.datemask";
    private static final String ISWE_HTTPUNIT_POOLDADOS_DB_DATETIMEMASK = "iswe.httpunit.pooldados.db.datetimemask";
    private static final String ISWE_HTTPUNIT_POOLDADOS_DB_DATEMASK = "iswe.httpunit.pooldados.db.datemask";
    private static final String __ALTERADO = "__Alterado";
    private static final String ISWE_HTTPUNIT_POOLDADOS_DB_PASSWORD = "iswe.httpunit.pooldados.db.password";
    private static final String ISWE_HTTPUNIT_POOLDADOS_DB_USER = "iswe.httpunit.pooldados.db.user";
    private static final String ISWE_HTTPUNIT_POOLDADOS_DB_URL = "iswe.httpunit.pooldados.db.url";
    private static final String ISWE_HTTPUNIT_POOLDADOS_DB_DRIVERCLASS = "iswe.httpunit.pooldados.db.driverclass";
    private static final String WEB_TEST_INFO = "WebTestInfo";

    private static Log log = LogFactory.getLog(TestSuiteDataManager.class);
    
    //Manter um unico dataManager por thread em execucao.
    
	private static final Map<Class, TestDataManager> testManagerPool = new HashMap<Class, TestDataManager>();
    
    private InputStream[] arquivos = null;
    private Properties properties;
    
    private TestDataManager(InputStream[] arquivos, Properties properties){
        log.info("Criando novo TestDataManager");
        this.arquivos = arquivos;
        this.properties = properties;
    }
    
    private Integer loops;
    private String urlHost;
    private Long timeBetweenUsers;
    //lista de dados de input por usuarios que seram executados.
    private List<Map<String, String>> dadosUsuarios = new ArrayList<Map<String,String>>();
    //lista de tabelas q devem ser inseridos para a execucao do caso de uso.
    private Map<String, List<Map<String, String>>> dadosTabelas = new LinkedHashMap<String, List<Map<String, String>>>();
    //SQL para apagar os dados criados pelo pool de dados
    private List<String> listaSqlDelete = new ArrayList<String>();
    
    /**
     * Metodo que carrega os dados do Excel para memoria.
     *
     */
    private void parseXlsData() {
        //utilizar POI para fazer o parser do arquivo xls.
        for(InputStream arq : arquivos){
            try{
                    //Abre o arquivo
                    POIFSFileSystem fs = new POIFSFileSystem(arq);
                    //Abre o workbook
                    HSSFWorkbook wb = new HSSFWorkbook(fs);

                    //varre as planilhas dentro do arquivo
                    for(int sheetId = 0; sheetId < wb.getNumberOfSheets();sheetId++){
                        HSSFSheet sheet = wb.getSheetAt(sheetId);
                        //verifica o nome da sheet
                        String sheetName = wb.getSheetName(sheetId);
                        //caso seja uma sheet : WebTestInfo
                        if(WEB_TEST_INFO.equals( sheetName )){
                            ///////////////////////////////////////
                            //Ler informacoes basicas do test web//
                            //URL Host
                            this.urlHost = properties.getProperty(ISWE_HTTPUNIT_HOST_URL);
                            //Loops
                            this.loops = ((Double) sheet.getRow(2).getCell((short)1).getNumericCellValue() ).intValue();
                            //Tempo entre usuarios
                            this.timeBetweenUsers = ((Double) sheet.getRow(3).getCell((short)1).getNumericCellValue() ).longValue() * 1000;
                            
                            //////////////////////////////////////////////////
                            //carrega os campos e os valores para cada teste//
                            List<InfoCampo> campos = new ArrayList<InfoCampo>();
                            HSSFRow linha = sheet.getRow(6);
                            //varre ate nao ter mais campos
                            for(short cellId = linha.getFirstCellNum(); cellId < linha.getLastCellNum() + 2; cellId++){
                                HSSFCell cell = linha.getCell(cellId);
                                String cellValue = getCellValue(cell, null);
                                if(cellValue != null && !"".equals(cellValue)){
                                    //caso tenha o nome do campo.
                                    campos.add(new InfoCampo(cellValue));
                                }
                                else{
                                    break;
                                }
                            }
                            
                            //varre as linhas para carregar os dados para todas as colunas e numero de execucoes de provas
                            for(int rowId = 7; rowId <  sheet.getPhysicalNumberOfRows() + 2; rowId++){
                                linha = sheet.getRow(rowId);
                                boolean linhaContemDados = false;
                                Map<String, String> bean = new HashMap<String, String>();
                                //obtem a primeira celula.
                                for(short cellId = linha.getFirstCellNum(); cellId < linha.getLastCellNum() + 2; cellId++){
                                    HSSFCell cell = linha.getCell(cellId);
                                    String cellValue = null;
                                    if(campos.size() > cellId){
                                        cellValue = getCellValue(cell, campos.get(cellId).tipoCampo);
                                    }
                                    else{
                                        cellValue = getCellValue(cell, null);
                                    }
                                    if(cellValue != null && !"".equals(cellValue)){
                                        //Obtem o valor da celula
                                        bean.put(campos.get(cellId).nomeCampo, cellValue);
                                        linhaContemDados = true;
                                    }
                                    else{
                                        break;
                                    }
                                }
                                dadosUsuarios.add(bean);
                                if(!linhaContemDados){
                                    break;
                                }
                            }
                        }
                        else{
                            /////////////////////////////////////
                            //E uma planilha de carga de tabela//
                            
                            //info da tabela
                            List<Map<String, String>> lstDados = new ArrayList<Map<String, String>>();
                            
                            //nome da tabela
                            dadosTabelas.put(sheetName, lstDados);
                            //lista de campos..
                            List<InfoCampo> campos = new ArrayList<InfoCampo>();
                            //varre as linhas e colunas
                            for(int rowId = 0; rowId <  sheet.getPhysicalNumberOfRows(); rowId++){
                                HSSFRow linha = sheet.getRow(rowId);
                                boolean linhaContemDados = false;
                                Map<String, String> bean = new LinkedHashMap<String, String>();
                                //obtem a primeira celula.
                                for(short cellId = linha.getFirstCellNum(); cellId < linha.getLastCellNum() + 2; cellId++){
                                    HSSFCell cell = linha.getCell(cellId);
                                    String cellValue = null;
                                    if(campos.size() > cellId){
                                        cellValue = getCellValue(cell, campos.get(cellId).tipoCampo);
                                    }
                                    else{
                                        cellValue = getCellValue(cell, null);
                                    }
                                    if(cellValue != null && !"".equals(cellValue)){
                                        //verifica se � a primeira linha
                                        if(rowId == 0){
                                            campos.add(new InfoCampo(cellValue));
                                        }
                                        else{
                                            //Obtem o valor da celula
                                            bean.put(campos.get(cellId).nomeCampo, cellValue);
                                        }
                                        linhaContemDados = true;
                                    }
                                    else{
                                        break;
                                    }
                                }
                                if(!bean.isEmpty()){
                                	lstDados.add(bean);
                                }
                                if(!linhaContemDados){
                                    break;
                                }
                            }
                            
                        }
                    }
                    //fecha o input stream.
                    arq.close();
            }
            catch(IOException fexp){
                log.error("Problema ao ler Arquivo(xls) de pool de dados!", fexp);
                throw new ISWEConfigurationException("Problema ao ler Arquivo(xls) de pool de dados!", fexp);
            }
        }
        
    }

    /**
     * M�todo que retorna o valor da celula
     * @param cell
     * @param tipo 
     * @return
     */
    private String getCellValue(HSSFCell cell, String tipo) {
        if(cell == null){
            return null;
        }
        if(tipo != null && "DATA".equals(tipo)){
            //tenta retornar como data
            try{
                Date data = cell.getDateCellValue();
                //verifica se existe info de horario
                Calendar cal = Calendar.getInstance();
                cal.setTime(data);
                //verifica se a data esta acima de 1990
                if(cal.get(Calendar.HOUR) > 0 ||  cal.get(Calendar.MINUTE) > 0 || cal.get(Calendar.SECOND) > 0){
                    return xlsDtHoraFmt.format(data);
                }
                else{
                    return xlsDtFmt.format(data);
                }
            }
            catch(Exception exp){
                //tenta retornar como numero
                log.error("Problemas ao obter dados da celula", exp);
                throw new ISWEException("Problemas ao obter dados da celula", exp);
            }
        }
        if(cell.getCellType() == HSSFCell.CELL_TYPE_STRING){
            return cell.getStringCellValue();
        }
        else if(cell.getCellType() == HSSFCell.CELL_TYPE_NUMERIC){
            Double valorDouble = cell.getNumericCellValue();
            if(valorDouble.doubleValue() > valorDouble.longValue()){
                return valorDouble.toString();
            }
            else{
                return String.valueOf( valorDouble.intValue() );
            }
        }
        else{
            return cell.getStringCellValue();
        }
    }

    public static TestDataManager getInstance(Class clazz, InputStream [] arquivos, Properties properties){
        if(testManagerPool.get(clazz) == null){
            TestDataManager temp = new TestDataManager(arquivos, properties);
            testManagerPool.put(clazz, temp);
            temp.initialize();
        }
        return testManagerPool.get(clazz);
    }
    
    private SimpleDateFormat dbDtFmt = null;
    private SimpleDateFormat dbDtHoraFmt = null;
    private SimpleDateFormat xlsDtFmt = null;
    private SimpleDateFormat xlsDtHoraFmt = null;
    private SimpleDateFormat browserDtFmt = null;
    private SimpleDateFormat browserDtHoraFmt = null;
    private SimpleDateFormat browserHoraFmt = null;
    
    private void initialize() {
        //Inicia os formatadores.
        dbDtFmt = new SimpleDateFormat(properties.getProperty(ISWE_HTTPUNIT_POOLDADOS_DB_DATEMASK));
        dbDtHoraFmt = new SimpleDateFormat(properties.getProperty(ISWE_HTTPUNIT_POOLDADOS_DB_DATETIMEMASK));
        xlsDtFmt = new SimpleDateFormat(properties.getProperty(ISWE_HTTPUNIT_POOLDADOS_XLS_DATEMASK));
        xlsDtHoraFmt = new SimpleDateFormat(properties.getProperty(ISWE_HTTPUNIT_POOLDADOS_XLS_DATETIMEMASK));
        browserDtFmt = new SimpleDateFormat(properties.getProperty(ISWE_HTTPUNIT_POOLDADOS_BROWSER_DATEMASK));
        browserDtHoraFmt = new SimpleDateFormat(properties.getProperty(ISWE_HTTPUNIT_POOLDADOS_BROWSER_DATETIMEMASK));
        browserHoraFmt = new SimpleDateFormat(properties.getProperty(ISWE_HTTPUNIT_POOLDADOS_BROWSER_TIMEMASK));
        
        parseXlsData();
        loadAllData();
    }

    public static TestDataManager getInstance(Class clazz){
        return testManagerPool.get(clazz);
    }
    
    public void loadAllData() {
        //carrega os dados das tabelas.
        loadTableData();
    }
    /** 
     * Metodo que carrega todos os dados necess�rio para os testes, executa as queries necessarias e mantem tudo em cache.
     */
    public void loadTableData() {
        //Carrega os dados das tabelas usando dbuntils
        Connection conn = null;
        StringBuffer insert = null;
    	try{
            conn = getDataSource().getConnection();
	    	for(String tabela : dadosTabelas.keySet()){
	    		List<Map<String, String>> dados = dadosTabelas.get(tabela);
	    		Statement stmtBatchInsert = conn.createStatement();
	    		Statement stmtDelete = conn.createStatement();
	    		for(Map<String, String> linha : dados){
	    			insert = new StringBuffer("INSERT INTO ");
		    		StringBuffer campos = new StringBuffer("( ");
		    		StringBuffer valores = new StringBuffer(" VALUES(");
	    			insert.append(tabela);
                    String campoChave = null;
                    String valorChave = null;
	    			for(String campo : linha.keySet()){
	    				campos.append(campo);
                        String valor = linha.get(campo);
                        valor = obterValorSQL(obterValor(valor));
	    				campos.append(",");
	    				valores.append(valor);
	    				valores.append(",");
                        if(campoChave == null){
                            campoChave = campo;
                            valorChave = valor;
                        }
	    			}
	    			insert.append(campos.substring(0, campos.length() -1));
	    			insert.append(") ");
	    			insert.append(valores.substring(0, valores.length() -1));
	    			insert.append(") ");
	    			
                    //cria o SQL de delete para este registro
                    StringBuffer sqlDelete = new StringBuffer("DELETE FROM ");
                    sqlDelete.append(tabela);
                    sqlDelete.append(" WHERE ");
                    sqlDelete.append(campoChave);
                    sqlDelete.append(" = ");
                    sqlDelete.append(valorChave);
                    listaSqlDelete.add(sqlDelete.toString());
                    
                    //apara o registro caso ja exista par eviar unique constraint
                    stmtDelete.executeUpdate(sqlDelete.toString());
                    
	    			//executa a querie
                    if(log.isInfoEnabled()){
                        log.info("Inserindo dados: " + insert.toString());
                    }
	    			//stmtBatchInsert.addBatch(insert.toString());
                    stmtBatchInsert.executeUpdate(insert.toString());
	    		}
	    		//insere os dados da tabela
	    		//stmtBatchInsert.executeBatch();
	    		stmtBatchInsert.close();
	    	}
    	}
    	catch(Exception exp){
            log.error("Problemas ao tentar carregar tabelas do pool de dados: SQL: " + insert.toString(), exp);
    		throw new ISWEException("Problemas ao tentar carregar tabelas do pool de dados: SQL: " + insert.toString(), exp);
    	}
        finally{
            if(conn != null){
                try {
                    conn.close();
                }
                catch (SQLException e) {
                    log.error("Problemas ao tentar fechar conexao!", e);
                }
            }
        }
    }

    private static final String dataPattern = "^\\d{2}/\\d{2}/\\d{4}$";
    private static final String dataHoraPattern = "^\\d{2}/\\d{2}/\\d{4} \\d{2}:\\d{2}$";
    private static final String inteiroPattern = "^\\d*$";
    private static final String decimalPatternVirgula = "^\\d*,\\d*$";
    private static final String decimalPatternPronto = "^\\d*\\.\\d*$";
    private static final String queryReferencePattern = "^#QUERY:.*$";

	private Object obterValor(String valor) {
		try{
			if(valor == null){
				return null;
			}
			//Identifica o valor
			//verifica se é uma data
			if(valor.matches(dataPattern)){
				return xlsDtFmt.parse(valor);
			}
			else if(valor.matches(dataHoraPattern)){
				return new Timestamp( xlsDtHoraFmt.parse(valor).getTime() );
			}
			else if(valor.matches(inteiroPattern)){
				return new Integer(valor);
			}
			else if( valor.matches(decimalPatternVirgula) || valor.matches(decimalPatternPronto) ){
				return new Double(valor.replace(',', '.'));
			}
			else if( valor.matches(queryReferencePattern)){
				return obterValor( executeQuerie(valor) );
			}
			else{
				return valor;
			}
		}
		catch(Exception exp){
            log.error("Problemas ao realizar parser do valor: " + valor, exp);
			throw new ISWEException("Problemas ao realizar parser do valor: " + valor, exp);
		}
	}
	
	private String obterValorSQL(Object valor) {
		//verifica o tipo de dados
		if(valor instanceof String){
			return "'" + valor.toString() + "'";
		}
		else if(valor instanceof Integer){
			return valor.toString();
		}
		else if(valor instanceof Double){
			return valor.toString();
		}
        else if(valor instanceof Date){
            return "'" + dbDtFmt.format((Date)valor) + "'";
        }
        else if(valor instanceof Timestamp){
            return "'" + dbDtHoraFmt.format((Date)valor) + "'";
        }
		else{
			return "'" + valor.toString() + "'";
		}
	}

	/**
	 * Método que executa uma querie num parametro.
	 * @param querie
	 * @return
	 */
	private String executeQuerie(String querie) {
		try{
			//Faz o parser do valor e executa a querie
			String [] itens = querie.split(":");
			StringBuffer sql = new StringBuffer("SELECT * FROM ");
			sql.append(itens[1]);
			sql.append(" WHERE ");
			sql.append(itens[2]);
			
			ScalarHandler sHnd = new ScalarHandler(){
				@Override
				public Object handle(ResultSet rs) throws SQLException {
					if(rs.next()){
						return rs.getString(1);
					}
					return null;
				}
			};
			//query runner
			QueryRunner qRunner = new QueryRunner(getDataSource());
            if(log.isInfoEnabled()){
                log.info("Execute dados: " + sql.toString());
            }
			String retorno = (String)qRunner.query(sql.toString(), sHnd);
			//
			return retorno;
		}
		catch(Exception exp){
            log.error("Problemas ao realizar parser do querie: " + querie, exp);
			throw new ISWEException("Problemas ao realizar parser do querie: " + querie, exp);
		}
	}

	private DataSource getDataSource() {
		try{
			//Obtem um datasource
			BasicDataSource ds = new BasicDataSource();
            String driverClass = properties.getProperty(ISWE_HTTPUNIT_POOLDADOS_DB_DRIVERCLASS);
			Class.forName(driverClass);
			ds.setDriverClassName(driverClass);
			ds.setUrl(properties.getProperty(ISWE_HTTPUNIT_POOLDADOS_DB_URL));
			ds.setUsername(properties.getProperty(ISWE_HTTPUNIT_POOLDADOS_DB_USER));
			ds.setPassword(properties.getProperty(ISWE_HTTPUNIT_POOLDADOS_DB_PASSWORD));
			return ds;
		}
		catch(ClassNotFoundException exp){
            log.error("Class do driver especificado nao existe!", exp);
			throw new ISWEConfigurationException("Class do driver especificado nao existe!", exp);
		}
	}

	/**
     * M�todo responsavel por limpar todos os dados criados pelo TesDataManger.
     *
     */
    public void clearAllData() {
        //varre a lista de sql deletes
        Connection conn = null;
        try{
            conn = getDataSource().getConnection();
            Statement stmt = conn.createStatement();
            int idx = listaSqlDelete.size() -1;
            if(idx > 0){
                for(; idx >= 0; idx--){
                    String sql = listaSqlDelete.get(idx);
                    if(log.isInfoEnabled()){
                        log.info("Limpando dados: " + sql);
                    }
                    stmt.executeUpdate(sql);
                }
            }
            stmt.close();
        }
        catch(Exception exp){
            log.error("Problemas ao tentar executar sql Deletes para limpar os dados do Pool!", exp);
            throw new ISWEException("Problemas ao tentar executar sql Deletes para limpar os dados do Pool!", exp);
        }
        finally{
            if(conn != null){
                try {
                    conn.close();
                }
                catch (SQLException e) {
                    log.error("Problemas ao tentar fechar conexao!", e);
                }
            }
        }
    }

    /**
     * M�todo que retorna um valor String de acordo com o numero da thread e de acordo com a propriedade.
     * @param threadNum 
     * @param string
     * @return
     */
    public String getStringValue(int threadNum, String property) {
        //verifica se a threadNum esta dentro do aceitavel
        if(threadNum > getNumberOfUsers() - 1){
            log.error("O Numero de threadNum informado esta fora do range.");
            throw new ISWEBadCodeException("O Numero de threadNum informado esta fora do range.", null);
        }
        
        Map<String, String> dados = dadosUsuarios.get(threadNum);
        String valor = dados.get(property);
        //verifica se é uma querie
        if(valor != null && valor.matches( queryReferencePattern )){
        	valor = executeQuerie(valor);
        }
        return valor;
    }

    public String getNumberValue(int threadNum, String property) {
        return getStringValue(threadNum, property);
    }
    
    public String getBooleanValue(Integer threadNum, String property) {
        return getStringValue(threadNum, property);
    }
    
    public String getDateTimeValue(int threadNum, String property) {
    	String valor = getStringValue(threadNum, property);
    	Date date;
		try {
			date = xlsDtHoraFmt.parse(valor);
		} catch (ParseException e) {
			log.error("Data em formato incorreto: " + valor, e);
			throw new ISWEException("Data em formato incorreto: " + valor, e);
		}
        return browserDtHoraFmt.format(date);
    }

    public String getDateValue(int threadNum, String property) {
    	String valor = getStringValue(threadNum, property);
    	Date date;
		try {
			date = xlsDtFmt.parse(valor);
		} catch (ParseException e) {
			log.error("Data em formato incorreto: " + valor, e);
			throw new ISWEException("Data em formato incorreto: " + valor, e);
		}
        return browserDtFmt.format(date);
    }

    public String getTimeValue(int threadNum, String property) {
    	String valor = getStringValue(threadNum, property);
    	Date date;
		try {
			date = xlsDtHoraFmt.parse(valor);
		} catch (ParseException e) {
			log.error("Data em formato incorreto: " + valor, e);
			throw new ISWEException("Data em formato incorreto: " + valor, e);
		}
        return browserHoraFmt.format(date);
    }

    public String getPostFixAlteracao() {
        return __ALTERADO;
    }

    public Long getTimeBetweenUsers() {
        return timeBetweenUsers;
    }

    public Integer getNumberOfUsers() {
        return dadosUsuarios.size();
    }

    public String getUrlHost() {
        return urlHost;
    }

    public Integer getLoops() {
        return loops;
    }

    public void setLoops(Integer loops) {
        this.loops = loops;
    }

    public void setTimeBetweenUsers(Long timeBetweenUsers) {
        this.timeBetweenUsers = timeBetweenUsers;
    }

    public void setUrlHost(String urlHost) {
        this.urlHost = urlHost;
    }

    /**
     * Metodo que finaliza o testDataManager e limpar todos os dados inseridos pelo pool.
     */
    public void end() {
        //
        clearAllData();
    }
}


class InfoCampo{
    public InfoCampo(String cellValue) {
        //Varifica se existe informacao de tipo
        String [] dados = cellValue.split(":");
        nomeCampo = dados[0];
        if(dados.length > 1){
            tipoCampo = dados[1];
        }
    }
    protected String nomeCampo;
    protected String tipoCampo;
}